package com.czk.tx.service;

import com.czk.tx.dao.MoneyDao;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 一、PlatformTransactionManager 管理事务的接口
 * 针对不同的框架有不同的实现类
 * 1 CciLocalTransactionManager
 * 2 JpaTransactionManager jpa
 * 3 DataSourceTransactionManager jdbc mybatis
 * 4 WebLogicJtaTransactionManager
 * 5 WebSphereUowTransactionManager
 * 5 HibernateTransactionManager hibernate框架
 * 二、 事务的传播行为
 * 1 Propagation.REQUIRED
 * 如果add()有事务，调用update方法时，则update事务使用add的事务
 * 如果add()，没有事务，则新起一个update的事务
 * 2 Propagation.NEW
 * 使用add方法调用update方法时，无论update是否有事务，都需要新建事务
 * 3 Propagation.support
 * 支持事务，但是不会新开一个事务起来
 * 4 Propagation.not_support
 * 不支持事务，此时会将methodA的事务挂起
 * 5 Propagation.never
 * 不支持事务，有事务则抛出异常
 * 6 Propagation.MANDATORY
 * 支持事务，如果methodA没有事务则会抛出异常
 * 7 Propagation.Nested
 * 支持事务，如果没有则新建一个事务，如果有，则就在当前事务嵌套新的事务
 * 三、事物的隔离级别
 * 三个读问题：脏读、虚读、不可重复读
 * ① 脏读：一个未提交的事务读取到了另一个未提交事务的数据
 * ② 不可重复读：一个未提交的事务读到了已经提交事务修改数据
 * ③ 幻读：一个未提交的事务读到了已经提交事务添加的数据
 *
 * @Author:ChenZhangKun
 * @Date: 2020/11/24 20:18
 */
@Service
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@EnableAspectJAutoProxy(exposeProxy = true)
public class MoneyService {
    @Autowired
    private MoneyDao moneyDao;
    private ReentrantLock lock = new ReentrantLock();

    /**
     * 进行转账
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void traverse() {
        moneyDao.add();
        moneyDao.reduce();
        // 此时不会报错，因为此时是this调用，不是代理对象，所以没有aop代理
        // traverse_1();
        // 此时会报错
        MoneyService o = (MoneyService) AopContext.currentProxy();
        o.traverse_1();
    }

    @Transactional(propagation = Propagation.NEVER)
    public void traverse_1() {
        System.out.println("调用了");
    }

    /**
     * 带事务的并发编程测试
     * 下面这种方式的问题
     * 1 事务的开启->执行第一条sql语句的时候，才是肯定是在lock.lock()之后
     * 2 事务的提交->方法执行完毕后提交，此时会在unlock之后，此时会造成事务并未提交，其他线程的修改问题，造成数据损坏
     * 3 解决方法->将其放在try，如方法lockTransactionSolution所示
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void lockTransaction() {
        try {
            lock.lock();
            // 指向业务
            // 1 银行扣减余额
            decrease();
            // 2 个人增加余额
            increase();
        } finally {
            lock.unlock();
        }
        System.out.println("increase=" + increase);
        System.out.println("decrease=" + decrease);
    }

    int increase = 0;
    int decrease = 100;

    private void increase() {
        increase++;
        System.out.println("增加余额成功");
    }

    private void decrease() {
        decrease--;
        System.out.println("扣减余额成功");
    }

    public void lockTransactionSolution() {
        try {
            lock.lock();
            // 放在里面防止使其提交在锁释放之前
            doDecreaseAndIncrease();
        } finally {
            lock.unlock();
        }
        System.out.println("increase=" + increase);
        System.out.println("decrease=" + decrease);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void doDecreaseAndIncrease() {
        decrease();
        increase();
    }
}
